1 module hip.assets.tilemap;
2 public import hip.api.data.tilemap;
3 public import hip.api.data.asset;
4 import hip.assets.texture;
5 import hip.config.opts;
6 import hip.assets.image;
7 import hip.data.json;
8 
9 class HipTileset : HipAsset, IHipTileset
10 {
11     uint _columns; uint columns() const =>_columns;
12 
13     ///Means where the tileset id starts
14     uint _firstGid; uint firstGid() const => _firstGid;
15     
16 
17     ///"image" in tiled
18 
19     string _texturePath; string texturePath() const => _texturePath;
20     ///"imageheight" in tiled
21     uint  _textureHeight; uint  textureHeight() const => _textureHeight;
22     ///"imagewidth" in tiled
23     uint  _textureWidth; uint  textureWidth() const => _textureWidth;
24     IHipTexture _texture; IHipTexture texture()     => _texture;
25     int _margin; int margin() const => _margin;
26 
27     override string name() const => super.name;
28 
29     ///Only available when loaded via .tsx
30     string _path; string path() const => _path;
31     int _spacing; int spacing() const => _spacing;
32     uint _tileHeight; uint tileHeight() const => _tileHeight;
33     uint _tileWidth; uint tileWidth() const => _tileWidth;
34     Tile[] _tiles; Tile[] tiles() => _tiles;
35 
36     void setTexture(IHipTexture texture)
37     {
38         this._texture = texture;
39         this._textureWidth = texture.getWidth;
40         this._textureHeight = texture.getHeight;
41     }
42 
43     // static if(hasTSXSupport)
44     // {
45     //     import arsd.dom;
46 
47     //     static Tileset fromTSX(ubyte[] tsxData, string tsxPath, bool autoLoadTexture = true)
48     //     {
49     //         string xmlFile = cast(string)tsxData;
50     //         auto document = new XmlDocument(xmlFile);
51     //         auto tileset = document.querySelector("tileset");
52     //         return Tileset.fromXMLElement(tileset, tsxPath, autoLoadTexture);
53     //     }
54 
55 
56     //     static Tileset fromXMLElement(Element tileset, string tsxPath="", bool autoLoadTexture=true)
57     //     {
58     //         auto image   = tileset.querySelector("image");
59 
60     //         const uint tileCount = to!uint(tileset.getAttribute("tilecount"));
61     //         Tileset ret = new Tileset(tileCount);
62     //         ret.path = tsxPath;
63 
64     //         //Tileset
65     //         ret.name        =         tileset.getAttribute("name");
66     //         ret.tileWidth   = to!uint(tileset.getAttribute("tilewidth"));
67     //         ret.tileHeight  = to!uint(tileset.getAttribute("tileheight"));
68     //         ret.columns     = to!uint(tileset.getAttribute("columns"));
69 
70     //         //Image
71     //         ret.texturePath   =         image.getAttribute("source");
72     //         ret.textureWidth  = to!uint(image.getAttribute("width"));
73     //         ret.textureHeight = to!uint(image.getAttribute("height"));
74 
75     //         if(autoLoadTexture)
76     //             ret.loadTexture();
77 
78     //         Element[] tiles = tileset.querySelectorAll("tile");
79 
80     //         foreach(t; tiles)
81     //         {
82     //             Tile tile;
83     //             tile.id = to!ushort(t.getAttribute("id"));
84     //             Element anim = t.querySelector("animation");
85     //             if(anim !is null)
86     //             {
87     //                 Element[] frames = anim.querySelectorAll("frame");
88     //                 tile.animation = new TileAnimationFrame[frames.length];
89 
90     //                 foreach(f; frames)
91     //                 {
92     //                     TileAnimationFrame tFrame;
93     //                     tFrame.id       = to!ushort(f.getAttribute("tileid"));
94     //                     tFrame.duration =    to!int(f.getAttribute("duration"));
95     //                 }
96     //             }
97     //         }
98 
99     //         return ret;
100     //     }
101 
102 
103     //     static Tileset fromTSX(string tsxPath, bool autoLoadTexture = true)
104     //     {
105     //         void[] tsxData;
106     //         if(!HipFS.read(tsxPath, tsxData))
107     //         {
108     //             import hip.error.handler;
109     //             ErrorHandler.showWarningMessage("Could not load TSX ", tsxPath);
110     //             return null;
111     //         }
112     //         return fromTSX(cast(ubyte[])tsxData, tsxPath, autoLoadTexture);
113     //     }
114 
115     //     protected static TileLayer tileLayerFromElement(Element l)
116     //     {
117     //         import hip.util.string:split;
118     //         import hip.util.file:stripLineBreaks;
119     //         TileLayer layer = new TileLayer();
120     //         layer.type    = TileLayerType.TILE_LAYER;
121     //         layer.id      = to!ushort(l.getAttribute("id"));
122     //         layer.name    =           l.getAttribute("name");
123     //         layer.width   =   to!uint(l.getAttribute("width"));
124     //         layer.height  =   to!uint(l.getAttribute("height"));
125     //         string[] data = l.querySelector("data").innerText.stripLineBreaks.split(",");
126     //         layer.tiles.reserve(data.length);
127     //         for(int i = 0; i < data.length;i++)
128     //             layer.tiles~=to!ushort(data[i]);
129 
130     //         return layer;
131     //     }
132 
133     //     protected static TileLayer objectLayerFromElement(Element objgroup)
134     //     {
135     //         TileLayer layer = new TileLayer();
136     //         layer.type = TileLayerType.OBJECT_LAYER;
137     //         layer.id   = toDefault!(ushort)(objgroup.getAttribute("id"));
138     //         layer.name = objgroup.getAttribute("name");
139     //         Element[] objs = objgroup.querySelectorAll("object");
140     //         foreach(o; objs)
141     //         {
142     //             TileLayerObject obj;
143     //             obj.gid     = toDefault!(ushort)(o.getAttribute("gid"));
144     //             obj.height  =   toDefault!(uint)(o.getAttribute("height"));
145     //             obj.id      = toDefault!(ushort)(o.getAttribute("id"));
146     //             obj.name    =            (o.getAttribute("name"));
147     //             obj.rotation=    toDefault!(int)(o.getAttribute("rotation"));
148     //             obj.type    =            (o.getAttribute("type"));
149     //             obj.visible =   toDefault!(bool)(o.getAttribute("visible"));
150     //             obj.width   =   toDefault!(uint)(o.getAttribute("width"));
151     //             obj.x       =    toDefault!(int)(o.getAttribute("x"));
152     //             obj.y       =    toDefault!(int)(o.getAttribute("y"));
153     //             Element[] props = o.querySelectorAll("properties");
154     //             foreach(p; props)
155     //             {
156     //                 TileProperty tp;
157     //                 tp.name  = p.getAttribute("name");
158     //                 tp.type  = p.getAttribute("type");
159     //                 tp.value = p.getAttribute("value");
160     //                 obj.properties[tp.name] = tp;
161     //             }
162     //         }
163     //         return layer;
164     //     }
165     //     static Tilemap readTiledTMX(string tiledPath)
166     //     {
167     //         void[] tmxData;
168     //         if(!HipFS.read(tiledPath, tmxData))
169     //         {
170     //             import hip.error.handler;
171     //             ErrorHandler.showWarningMessage("Could not read Tiled TMX from path ", tiledPath);
172     //             return null;
173     //         }
174     //         return readTiledTMX(cast(ubyte[])tmxData, tiledPath);
175     //     }
176 
177     //     static Tilemap readTiledTMX(ubyte[] tiledData, string tiledPath, bool autoLoadTexture = true)
178     //     {
179     //         Tilemap ret = new Tilemap();
180     //         string xmlFile = cast(string)tiledData;
181     //         auto document = new XmlDocument(xmlFile);
182     //         auto map = document.querySelector("map");
183     //         ret.path = tiledPath;
184 
185     //         ret.tiled_version =         map.getAttribute("tiledVersion");
186     //         ret.orientation   =         map.getAttribute("orientation");
187     //         ret.width         = to!uint(map.getAttribute("width"));
188     //         ret.height        = to!uint(map.getAttribute("height"));
189     //         ret.tileWidth     = to!uint(map.getAttribute("tilewidth"));
190     //         ret.tileHeight    = to!uint(map.getAttribute("tileheight"));
191     //         ret.isInfinite    = (to!uint(map.getAttribute("infinite")) == 1);
192     //         ret.renderorder   =         map.getAttribute("renderorder");
193 
194     //         auto tileset = document.querySelectorAll("tileset");
195 
196     //         foreach(t; tileset)
197     //         {
198     //             string tsxPath = t.getAttribute("source");
199     //             Tileset set;
200     //             if(tsxPath != null)
201     //                 set = Tileset.fromTSX(ret.getTSXPath(tsxPath), autoLoadTexture);
202     //             else
203     //             {
204     //                 set = Tileset.fromXMLElement(t, ret.getTSXPath("null"));
205     //                 //Using getTSXPath with any string, as it will be replaced later
206     //                 //For the texture path
207     //             }
208     //             set.firstGid = to!uint(t.getAttribute("firstgid"));
209     //             ret.tilesets~=set;
210     //         }
211 
212     //         auto layers = document.querySelectorAll("map > layer");
213     //         foreach(l; layers)
214     //         {
215     //             TileLayer layer = Tilemap.tileLayerFromElement(l);
216     //             ret.layersArray~= layer;
217     //             ret.layers[layer.name] = layer;
218     //         }
219 
220     //         Element[] objGroups = document.querySelectorAll("map > objectgroup");
221     //         foreach(objGroup; objGroups)
222     //         {
223     //             TileLayer layer = Tilemap.objectLayerFromElement(objGroup);
224     //             ret.layers[layer.name] = layer;
225     //         }
226 
227     //         return ret;
228     //     }
229 
230 
231     // }
232     // else static if(Version.HipTSX)
233     // {
234     //     static assert(false, `Please call dub add arsd-official:dom for using TSX parser`);
235     // }
236 
237     static HipTileset read (string path, void delegate(HipTileset self) onSuccess, void delegate() onError, uint firstGid = 1)
238     {
239         import hip.util.path;
240         switch(path.extension)
241         {
242             case "xml":
243             case "tmx":
244                 throw new Exception("TMX/TSX parser was removed for making the engine smaller.");
245             case "tsj":
246             case "json":
247                 return HipTileset.readJSON(path, firstGid, onSuccess, onError);
248             default:
249                 assert(false, "Unrecognized extension for file "~path);
250         }
251     }
252 
253     static HipTileset readFromMemory (string path, string data, void delegate(HipTileset) onSuccess, void delegate() onError, uint firstGid = 1)
254     {
255         import hip.util.path;
256         switch(path.extension)
257         {
258             case "xml":
259             case "tmx":
260                 throw new Exception("TMX/TSX parser was removed for making the engine smaller.");
261             case "tsj":
262             case "json":
263                 HipTileset ret = new HipTileset(0);
264                 ret._path = path;
265                 ret._firstGid = firstGid;
266                 ret.loadJSON(parseJSON(data), onSuccess, onError);
267                 return ret;
268             default:
269                 assert(false, "Unrecognized extension for file "~path);
270         }
271     }
272 
273     static HipTileset readJSON (string path, uint firstGid, void delegate(HipTileset self) onSuccess, void delegate() onError)
274     {
275         import hip.api.filesystem.hipfs;
276         import hip.console.log;
277         HipTileset tileset = new HipTileset(0);
278         tileset._path = path;
279         tileset._firstGid = firstGid;
280 
281         HipFS.readText(path).addOnSuccess((in void[] data)
282         {
283             tileset.loadJSON(parseJSON(cast(string)data), onSuccess, onError);
284             return FileReadResult.free;
285         }).addOnError((err)
286         {
287             loglnWarn("Could not read file at path ", path," ", err);
288         });
289         return tileset;
290     }
291 
292     public static HipTileset readJSON (string path, uint firstGid, const JSONValue t, void delegate(HipTileset self) onSuccess, void delegate() onError)
293     {
294         HipTileset ret = new HipTileset(0);
295         ret._path = path;
296         ret._firstGid = firstGid;
297         ret.loadJSON(t, onSuccess, onError);
298         return ret;
299     }
300 
301     private void loadJSON (const JSONValue t, void delegate(HipTileset self) onSuccess, void delegate() onError)
302     {
303         import hip.util.path;
304         if(t.hasErrorOccurred)
305         {
306             import hip.error.handler;
307             ErrorHandler.showErrorMessage("JSON Parsing Error on Tilemap", t.toString);
308             return onError();
309         }
310 
311         _tiles = new Tile[cast(uint)t["tilecount"].integer];
312         _texturePath   =             t["image"].str;
313         if(!isAbsolutePath(_texturePath) && _path.length)
314             _texturePath = joinPath(dirName(_path), _texturePath).normalizePath;
315         _textureHeight =   cast(uint)t["imageheight"].integer;
316         _textureWidth  =   cast(uint)t["imagewidth"].integer;
317         _columns       = cast(ushort)t["columns"].integer;
318         _margin        =    cast(int)t["margin"].integer;
319         _name          =             t["name"].str;
320         _spacing       =    cast(int)t["spacing"].integer;
321         _tileHeight    =   cast(uint)t["tileheight"].integer;
322         _tileWidth     =   cast(uint)t["tilewidth"].integer;
323 
324         if("tiles" in t)
325         {
326             foreach (currentTile; t["tiles"].array)
327             {
328                 Tile tile;
329                 tile.id = cast(ushort)currentTile["id"].integer;
330 
331                 foreach(prop; currentTile["properties"].array)
332                 {
333                     tile.properties[prop["name"].str] = propFromJSON(prop);
334                 }
335                 tiles[tile.id] = tile;
336             }
337         }
338         onSuccess(this);
339     }
340 
341 
342     import hip.util.data_structures;
343     static IHipTileset fromSpritesheet(Array2D_GC!IHipTextureRegion regions)
344     {
345         import hip.error.handler;
346         import hip.assets.texture;
347         ErrorHandler.assertExit(regions.getWidth > 0 && regions.getHeight > 0, "Invalid spritesheet");
348         HipTileset t = new HipTileset(regions.getWidth * regions.getHeight);
349         t._name = "Created from Spritesheet";
350         t._firstGid = 1;
351         t.setTexture(regions[0,0].getTexture);
352         t._tileWidth = regions[0,0].getWidth();
353         t._tileHeight = regions[0,0].getHeight();
354         int i = 0;
355         for(int y = 0; y < regions.getHeight; y++)
356             for(int x = 0; x < regions.getWidth; x++)
357             {
358                 Tile* tile = &t.tiles[i++];
359                 tile.id = cast(ushort)i;
360                 tile.region = regions[x, y]; //TODO: May use clone one day if direct assign doesn't fit
361                 // t.region = (cast(HipTextureRegion)regions[x, y]).clone;
362             }
363 
364         return t;
365     }
366     import hip.assets.textureatlas;
367     /**
368     *   Untested. D's Associative Arrays aren't deterministic, this is subject to bug.
369     */
370     static IHipTileset fromAtlas(HipTextureAtlas atlas)
371     {
372         HipTileset t = new HipTileset(cast(uint)atlas.frames.length);
373         t._firstGid = 1;
374         t._name = "Tileset from Atlas: "~atlas.name;
375         t.setTexture(atlas.texture);
376         int i = 0;
377         foreach(atlasFrame; atlas)
378         {
379             Tile* tile = &t.tiles[i++];
380             if(!t.tileWidth)
381             {
382                 t._tileWidth = atlasFrame.region.getWidth;
383                 t._tileHeight = atlasFrame.region.getHeight;
384             }
385             //TODO: May use clone one day if direct assign doesn't fit.
386             tile.region = atlasFrame.region;
387             tile.id = cast(ushort)i;
388         }
389         return t;
390     }
391 
392     this(uint tileCount)
393     {
394         super("HipTileset");
395         _tiles = new Tile[tileCount];
396         _typeID = assetTypeID!HipTileset;
397     }
398     IImage textureImage;
399 
400     IImage loadImage(void delegate(IImage self) onSuccess, void delegate() onFailure)
401     {
402         import hip.error.handler;
403         import hip.api.filesystem.hipfs;
404         import hip.util.path;
405         if(textureImage is null)
406         {
407             ErrorHandler.assertExit(texturePath != "", "No texture path for loading tilemap texture");
408             HipFS.read(texturePath)
409             .addOnError((string err)
410             {
411                 ErrorHandler.showErrorMessage("Error loading image required by Tileset: "~texturePath, err);
412                 onFailure();
413             })
414             .addOnSuccess((in ubyte[] imgData)
415             {
416                 textureImage = new Image(texturePath, cast(ubyte[])imgData, onSuccess, onFailure);
417                 return FileReadResult.free;
418             });
419         }
420         return textureImage;
421     }
422 
423     bool loadTexture()
424     {
425         import hip.error.handler;
426         import hip.assets.texture;
427         if(textureImage is null)
428         {
429             loadImage((_){loadTexture();}, (){});
430             return false;
431         }
432         _texture = new HipTexture(textureImage);
433         int i = 0;
434         for(int y = margin; y < textureHeight; y+= (tileHeight+spacing))
435             for(int x = margin, currCol = 0 ; currCol < columns; currCol++, x+= (tileWidth+spacing))
436             {
437                 Tile* t = &tiles[i];
438                 t.region = new HipTextureRegion(texture, x, y, x+tileWidth, y+tileHeight);
439                 i++;
440             }
441 
442         return texture !is null && texture.hasSuccessfullyLoaded();
443     }
444 
445     override void onFinishLoading(){}
446     override void onDispose(){}
447     override bool isReady() const {return _texture !is null;}
448 }
449 
450 
451 class HipTilemap : HipAsset, IHipTilemap
452 {
453 
454     int _x, _y;
455     HipColor _color = HipColor.white;
456     float _scaleX = 1.0, _scaleY = 1.0;
457     float _rotation = 0;
458 
459     string _path;
460     uint _width, _height;
461     bool _isInfinite;
462     HipTileLayer[string] _layers;
463     string _orientation;
464     string _renderOrder;
465     string _tiledVersion;
466     uint _tileWidth, _tileHeight;
467 
468     this(uint width = 0, uint height = 0, uint tileWidth = 0, uint tileHeight = 0)
469     {
470         super("HipTilemap");
471         _typeID = assetTypeID!HipTilemap;
472         this._width = width;
473         this._height = height;
474         this._tileWidth = tileWidth;
475         this._tileHeight = tileHeight;
476     }
477 
478     ref int x() => _x;
479     ref int y() => _y;
480     ref HipColor color() => _color;
481     ref float scaleX() => _scaleX;
482     ref float scaleY() => _scaleY;
483     float scale() => _scaleX;
484     float scale(float sc) => _scaleX = _scaleY = sc;
485     ref float rotation() => _rotation;
486 
487     ///Used for rendering order
488     string path() const => _path;
489     uint width() const => _width;
490     uint height() const => _height;
491     bool isInfinite() const => _isInfinite;
492     ref HipTileLayer[string] layers() => _layers;
493     string orientation() const => _orientation;
494     string renderorder() const => _renderOrder;
495     string tiled_version() const => _tiledVersion;
496     uint tileHeight() const => _tileHeight;
497     uint tileWidth() const => _tileWidth;
498 
499     void setTileSize(uint tileWidth, uint tileHeight)
500     {
501         _tileWidth = tileWidth;
502         _tileHeight = tileHeight;
503     }
504 
505     void addTileset(IHipTileset tileset){tilesets~= cast(HipTileset)tileset;}
506 
507     protected HipTileLayer[] layersArray;
508     HipTileset[] tilesets;
509     this()
510     {
511         super("HipTilemap");
512         _typeID = assetTypeID!HipTilemap;
513     }
514 
515     IHipTileset getTilesetForID(ushort id)
516     {
517         if(tilesets.length == 0)
518             return null;
519         for(int i = 0; i < cast(int)tilesets.length-1; i++)
520         {
521             if(id >= tilesets[i].firstGid && id < tilesets[i+1].firstGid)
522                 return tilesets[i];
523         }
524         return tilesets[$-1];
525     }
526 
527 
528     string getTSXPath(string tsxName)
529     {
530         import hip.util.path : replaceFileName;
531         return replaceFileName(path, tsxName);
532     }
533 
534     private static TiledObjectTypes typeInObject(JSONValue o)
535     {
536         with ( TiledObjectTypes )
537         {
538             if("ellipse" in o) return ellipse;
539             if("gid" in o) return tile;
540             if("point" in o) return point;
541             if("text" in o) return text;
542             if("polyline" in o) return line;
543             if("polygon" in o) return polygon;
544             return rect;
545         }
546     }
547 
548     private static void parseObjectLayer(ref HipTileLayer layer, JSONValue[] objects)
549     {
550         import hip.util.array:uninitializedArray;
551         int objIndex = 0;
552         layer.objects = uninitializedArray!(TiledObject[])(objects.length);
553         foreach(JSONValue o; objects)
554         {
555             TiledObject obj;
556 
557             obj.id      = cast(ushort)o["id"].integer;
558             obj.name    =             o["name"].str;
559             obj.type    =             o["type"].str;
560             obj.visible =             o["visible"].boolean;
561 
562 
563             obj.data.rect.x       = cast(int)   o["x"].floating;
564             obj.data.rect.rotation= cast(ushort)(o["rotation"].integer % 360);
565             obj.data.rect.y       = cast(int)   o["y"].floating;
566             obj.data.rect.height  = cast(uint)  o["height"].floating;
567             obj.data.rect.width   = cast(uint)  o["width"].floating;
568             obj.dataType = typeInObject(o);
569 
570             switch(obj.dataType) with(TiledObjectTypes)
571             {
572                 case text:
573                 {
574                     JSONValue txtObj = o["text"];
575                     obj.properties["__text"] = TileProperty(null, Variant.make(tryGetValue!string(txtObj, "text")));
576                     obj.properties["__fontfamily"] = TileProperty(null, Variant.make(tryGetValue!string(txtObj, "fontfamily")));
577                     obj.properties["__wrap"] = TileProperty(null,  Variant.make(tryGetValue!bool(txtObj, "wrap")));
578                 }
579                 break;
580                 case line:
581                 {
582                     JSONValue[] line = o["polyline"].array;
583                     int x = obj.data.rect.x;
584                     int y = obj.data.rect.y;
585                     foreach(i; 0..4)
586                         obj.data.rect.getLine[i] = tryGetValue(line[i/2], i % 2 == 0 ? "x" : "y", 0) + (i % 2 == 0 ? x : y);
587                     break;
588                 }
589                 case polygon:
590                 {
591                     JSONValue[] poly = o["polygon"].array;
592                     obj.dataType = poly.length == 3 ? TiledObjectTypes.triangle : TiledObjectTypes.polygon;
593                     int[2][] targetPoly = obj.data.triangle;
594                     if(poly.length > 3)
595                         targetPoly = new int[2][poly.length];
596                     foreach(i, v; poly)
597                     {
598                         targetPoly[i] = [
599                             tryGetValue(poly[i], "x", 0),
600                             tryGetValue(poly[i], "y", 0)
601                         ];
602                     }
603                     if(poly.length > 3)
604                         obj.data.polygon = targetPoly;
605                     break;
606                 }
607                 case tile:
608                     obj.tile.gid = cast(ushort)o["gid"].integer;
609                     break;
610                 default:break;
611             }
612             const(JSONValue)* v = ("properties" in o);
613             if(v != null)
614             {
615                 foreach(p; v.array) //Properties
616                     obj.properties[p["name"].str] = propFromJSON(p);
617             }
618             layer.objects[objIndex++] = obj;
619         }
620     }
621 
622     static HipTilemap readTiledJSON (string mapPath, const ubyte[] tiledData, void delegate(HipTilemap) onSuccess, void delegate() onError)
623     {
624         import hip.data.json;
625         HipTilemap ret = new HipTilemap();
626         ret._path = mapPath;
627         JSONValue json = parseJSON(cast(string)(tiledData));
628         ret._height     =    cast(uint)json["height"].integer;
629         ret._isInfinite =              json["infinite"].boolean;
630         ret._width      =    cast(uint)json["width"].integer;
631         ret._orientation=              json["orientation"].str;
632         ret._renderOrder=              json["renderorder"].str;
633         ret._tileHeight =    cast(uint)json["tileheight"].integer;
634         ret._tileWidth  =    cast(uint)json["tilewidth"].integer;
635 
636         foreach(l; json["layers"].array)
637         {
638             HipTileLayer layer = new HipTileLayer(ret);
639 
640             //Check first the layer type.
641             layer.name    =             l["name"].str;
642             layer.type    =             l["type"].str;
643             layer.id      = cast(ushort)l["id"].integer;
644             layer.opacity =             l["opacity"].integer;
645             layer.visible =             l["visible"].boolean;
646             layer.x       = cast(int)   l["x"].integer;
647             layer.y       = cast(int)   l["y"].integer;
648             if(layer.type == TileLayerType.OBJECT_LAYER)
649             {
650                 parseObjectLayer(layer, l["objects"].array);
651             }
652             else if(layer.type == TileLayerType.TILE_LAYER)
653             {
654                 auto layerData = l["data"].array;
655                 layer.height  = cast(uint)  l["height"].integer;
656                 layer.width   = cast(uint)  l["width"].integer;
657                 layer.tiles.reserve(layerData.length);
658                 foreach(d; layerData)
659                     layer.tiles~= cast(ushort)d.integer;
660             }
661 
662             const(JSONValue)* layerProp = ("properties" in l);
663             if(layerProp != null)
664             {
665                 foreach(p; layerProp.array)
666                     layer.properties[p["name"].str] = propFromJSON(p);
667             }
668             ret.layersArray~=layer;
669             ret._layers[layer.name] = layer;
670         }
671 
672         size_t maxTilesets = json["tilesets"].array.length;
673         auto onTilesetLoad = delegate(HipTileset tileset)
674         {
675             ret.tilesets~= tileset;
676             if(ret.tilesets.length == maxTilesets)
677                 onSuccess(ret);
678         };
679         foreach(t; json["tilesets"].array)
680         {
681             import hip.util.path;
682             const(JSONValue)* source = ("source" in t);
683             uint firstGid = cast(ushort)t["firstgid"].integer;
684 
685             ///Returns are being ignored since it is being handled on the onTilesetLoad
686             if(source !is null)
687             {
688                 import hip.console.log;
689                 loglnWarn("Reading from source: ", joinPath(dirName(mapPath), source.str).normalizePath);
690                 HipTileset.read(joinPath(dirName(mapPath), source.str).normalizePath, onTilesetLoad, onError, firstGid);
691             }
692             else
693                 HipTileset.readJSON(dirName(mapPath), firstGid, t, onTilesetLoad, onError);
694         }
695 
696 
697 
698         return ret;
699     }
700     static void readTiledJSON (string tiledPath, void delegate(HipTilemap) onSuccess, void delegate() onError)
701     {
702         import hip.api.filesystem.hipfs;
703         HipFS.read(tiledPath).addOnSuccess((in ubyte[] data)
704         {
705             HipTilemap.readTiledJSON(tiledPath, cast(ubyte[])data, onSuccess, onError);
706             return FileReadResult.free;
707         }).addOnError((err)
708         {
709             import hip.error.handler;
710             ErrorHandler.showWarningMessage("Could not read Tiled TMX from path ", tiledPath);
711             onError();
712         });
713     }
714 
715 
716     ///Those arguments are actually synchronous on all platforms. This is to simulate JS API.
717     void loadImages(void delegate() onSuccess, void delegate() onFailure)
718     {
719         int counter = 0;
720         auto onSuccessInternal = delegate(IImage _)
721         {
722             if(++counter == tilesets.length)
723                 onSuccess();
724         };
725         foreach(HipTileset tileset; tilesets)
726             tileset.loadImage(onSuccessInternal, onFailure);
727     }
728 
729     bool loadTextures()
730     {
731         foreach(HipTileset tileset; tilesets)
732             if(!tileset.loadTexture())
733                 return false;
734         return true;
735     }
736 
737     override void onFinishLoading(){}
738     override void onDispose(){}
739     override bool isReady() const {return true;}
740 
741 
742 }
743 
744 private TileProperty propFromJSON(JSONValue v)
745 {
746     import hip.util.exception;
747     JSONValue* t = "type" in v;
748     TileProperty ret = void;
749     enforce(t !is null, "propFromJSON must have a 'type'");
750     ret.type = t.str;
751     switch(ret.type)
752     {
753         case "object", "int":
754             ret.val = Variant.make(v["value"].integer);
755             break;
756         case "bool":
757             ret.val = Variant.make(v["value"].boolean);
758             break;
759         case "float":
760             ret.val = Variant.make(v["value"].floating);
761             break;
762         case "color", "string", "file":
763             ret.val = Variant.make(v["value"].str);
764             break;
765         default:
766             throw new Exception("Unknown property for TiledProperty of type "~ret.type);
767     }
768     return ret;
769 }